home *** CD-ROM | disk | FTP | other *** search
/ Cream of the Crop 22 / Cream of the Crop 22.iso / program / ctlib100.zip / INSTALL.LZH / ARRAYS.TXT next >
Text File  |  1996-10-12  |  18KB  |  368 lines

  1.                           CHAPTER 7
  2.                            Arrays
  3.  
  4. This chapter discusses in detail the array objects included in the Containers Library.  It describes how to use the arrays and the different types of arrays available.
  5.  
  6. In this chapter you will learn:
  7.  
  8.   - What are arrays?
  9.   - How to use the arrays
  10.   - The characteristics of each type of array
  11.  
  12.  
  13. What are arrays?
  14.  
  15. An array is a finite ordered group of homogeneous elements.  The ordered property means that it is possible to identify the first, second, third,  . . . , and n element in the array.  A dynamic array is an array for which a size can be set at runtime, instead of at compile time, as with standard arrays.  The simplest form of an array is the unidimensional array.
  16.  
  17. Arrays are the most simple of all data structures. Borland/Turbo Pascal compilers already include an array data type, but arrays of this type are limited in two aspects: the size of the arrays must be set at compile time, and they cannot be larger than 64k in size. Arrays in the Containers Library do not have these limitations and provide greater flexibility of use to the programmer.
  18.  
  19. There are two types of arrays implemented in the library: memory based arrays and stream based arrays.  Memory based arrays work very much like Borland arrays: they let you store and retrieve non-dynamically allocated data, although the use of pointers to some extent is required.  Standard arrays are faster than other arrays but still have the 64k limit, while huge arrays have no practical memory limit.
  20.  
  21. Stream arrays offer the greatest flexibility since you can choose where to store the array.  You can store the array in conventional memory, in expanded memory or even in a file if you wish to do so.  Unlike standard arrays, however, stream arrays require the use of dynamically allocated items.
  22.  
  23. There is also another type of array that is not static in size:  the resizable array.  This array gives the programmer a broader range of possibilities from which to choose.  Resizable arrays share all the characteristics of collections, but let you work with non-dynamically allocated data.  Sorted arrays are resizable arrays that let you sort and search items by key.
  24.  
  25. Like with normal arrays, when initializing an array container you must define the range of valid indexes for elements in the array and the individual size of those elements.  When using the normal array containers, you will always use the AtInsert method instead of the Insert method to add data to the array.  This is because normal array containers are static in size.  Using Insert will cause the array to attempt to insert the item at the position following the last valid element, producing a "range check" error.  When working with resizable arrays, you can use either the Insert or AtInsert methods to insert data into the array.
  26.  
  27.  
  28. Memory based arrays
  29.  
  30. Memory based arrays work very much like Borland arrays:  they let you work with data that is not dynamically allocated, in the same you way would work with an array allocated at compile-time.  Depending on the size of the array you want to use, you can choose from the standard arrays (which have a 64K size limit but are the fastest) and the huge arrays (which have no practical size limit).  In our examples, we will use a TStdArray;  however, we could have used a THugeArray instead with no modifications to the code.
  31.  
  32.  
  33. Creating a memory based array
  34.  
  35. As with the collections, to begin using an array you need to create the data type that you wish to store into the array.  Suppose we want to keep track of weather information at different hours in the day.  Since arrays work with non-dynamically allocated data, we will use a record type to store weather information for each hour:
  36.  
  37.   type
  38.     PWeatherInfo = ^TWeatherInfo;
  39.     TWeatherInfo = record
  40.       Location : string[20];
  41.       Humidity : Integer;
  42.       Rain : Integer;
  43.     end;
  44.  
  45. Notice that we also declared a pointer to the record type.  It is a good idea to always declare a pointer to the data type you are using, since it will facilitate your access to the data.  Now we will create and initialize the array that we will use:
  46.  
  47.   var
  48.     MorningWeatherData : PStdArray;
  49.     WeatherInfo: TWeatherInfo;
  50.  
  51.   begin  { arrays1.pas }
  52.     {...}
  53.     MorningWeatherData := New(PStdArray, Init(7, 11,
  54.       SizeOf(TWeatherInfo)));
  55.     {...}
  56.     Dispose(MorningWeatherData, Done);
  57.   end;
  58.  
  59. When creating an array, you must provide the starting and ending values for elements' indexes, and the size of each element.
  60.  
  61.  
  62. Using a memory based array
  63.  
  64. To insert data into the array, we simply fill an instance of a TWeatherInfo record with the appropriate data, and pass a pointer to it to the AtInsert method of the array:
  65.  
  66.   begin
  67.     {...}
  68.     with MorningWeatherData^ do
  69.     begin
  70.       { Sets the values of   }
  71.       { WeatherInfo's fields }
  72.       SetWeatherValues('Miami', 34, 0, WeatherInfo);
  73.       AtInsert(7, @WeatherInfo);
  74.       SetWeatherValues('Helsinski', 23, 3, WeatherInfo);
  75.       AtInsert(8, @WeatherInfo);
  76.       SetWeatherValues('Canada', 26, 2, WeatherInfo);
  77.       AtInsert(9, @WeatherInfo);
  78.       SetWeatherValues('Berlin', 28, 5, WeatherInfo);
  79.       AtInsert(10, @WeatherInfo);
  80.       SetWeatherValues('Melbourne', 20, 0, WeatherInfo);
  81.       AtInsert(11, @WeatherInfo);
  82.     end;
  83.     {...}
  84.   end.
  85.  
  86. You could also access the array data directly (see arrays2.pas), though this is not recommended to keep your applications as data-structure independent as possible.  For example:
  87.  
  88.   with MorningWeatherData^ do
  89.     with PWeatherInfo(At(7))^ do
  90.     begin
  91.       Location := Miami;
  92.       Humidity := 34;
  93.       Rain := 0;
  94.     end;
  95.  
  96.  
  97. Object arrays
  98.  
  99. Memory based object arrays are used to store non-dynamically allocated objects that are descendants of TObject.  Using these arrays is just like using the standard arrays.  However, you must be extra careful to always initialize the objects that you insert into the array.  Otherwise, the array will see the objects as uninitialized, and therefore, will not call their destructor methods when you dispose of the entire array.
  100.  
  101. We'll now rewrite our previous weather information example using objects instead of records.  In our TWeatherInfo object, we will use fields of type PString so that memory is only allocated for the portion of the string that is actually used.
  102.  
  103.   type
  104.     PWeatherInfo = ^TWeatherInfo;
  105.     TWeatherInfo = object(TObject)
  106.         Location : PString;
  107.         Humidity : Integer;
  108.         Rain : Integer;
  109.       constructor Init(ALocation : string; AHumidity,
  110.         ARain : Integer);
  111.       destructor Done; virtual;
  112.     end;
  113.  
  114.   constructor TWeatherInfo.Init(ALocation: string; AHumidity,
  115.     ARain : Integer);
  116.   begin
  117.     Location := NewStr(ALocation);
  118.     Humidity := AHumidity;
  119.     Rain := ARain;
  120.   end;
  121.  
  122.   destructor TWeatherInfo.Done;
  123.   begin
  124.     DisposeStr(Location);
  125.   end;
  126.  
  127. Then, we just create the object array and start inserting items into it.  Notice how we call the Init method for each object inserted into the array.
  128.  
  129.   begin  { arrays3.pas }
  130.     {...}
  131.     MorningWeatherData := New(PStdObjectArray, Init(7, 11,
  132.       SizeOf(TWeatherInfo)));
  133.  
  134.     { Insert the items in the array }
  135.     with MorningWeatherData^ do
  136.     begin
  137.       WeatherInfo.Init('Miami', 34, 0);
  138.       AtInsert(7, @WeatherInfo);
  139.       WeatherInfo.Init('Helsinski', 23, 3);
  140.       AtInsert(8, @WeatherInfo);
  141.       WeatherInfo.Init('Canada', 26, 2);
  142.       AtInsert(9, @WeatherInfo);
  143.       WeatherInfo.Init('Berlin', 28, 5);
  144.       AtInsert(10, @WeatherInfo);
  145.       WeatherInfo.Init('Melbourne', 20, 0);
  146.       AtInsert(11, @WeatherInfo);
  147.     end;
  148.     {...}
  149.  
  150.     { Dispose of the array }
  151.     Dispose(MorningWeatherData, Done);
  152.   end.
  153.  
  154. The last statement will dispose of the entire array and all the objects in it.  The array will call each object's Done destructor, but only if the object has been properly initialized.  If the array finds an object that has not been initialized, it will not call its Done destructor.
  155.  
  156. As with the standard arrays, you can also access the data in the array directly (see arrays4.pas).  For example, 
  157.  
  158.   with MorningWeatherData^ do
  159.     with PWeatherInfo(At(7))^ do
  160.       Init('Miami', 34, 0);
  161.  
  162. Again, this is not recommended to keep your applications as data structure independent as possible.
  163.  
  164.  
  165. Resizable arrays
  166.  
  167. Resizable arrays are a combination of the dynamically sizing capabilities of collections, with the ability of storing non-dynamically allocated data of standard arrays.
  168.  
  169. To show the use of resizable arrays, we'll use the contact list example used previously.  To store the data in the array, we'll use the following record structure:
  170.  
  171.   type
  172.     TContact = record
  173.       FirstName : string[15];
  174.       LastName : string[20];
  175.       Phone : string[18];
  176.       CompanyName : string [25];
  177.     end;
  178.  
  179. Creating a resizable array is like creating a collection: you must indicate the initial size of the array, and then the increments by which the array will grow.  To insert items into the array, you simply fill a record instance with the appropriate data and call the array's insert method:
  180.  
  181.   begin  { arrays5.pas }
  182.     {...}
  183.     ContactList := New(PResizableStdArray, Init(50, 10,
  184.       SizeOf(TContact)));
  185.  
  186.     { Insert the items in the array }
  187.     with ContactList^ do
  188.     begin
  189.       SetContactValues('Lewis', 'Carl', '(506) 83-780',
  190.         'Running, Corp.', Contact);
  191.       Insert(@Contact);
  192.       SetContactValues('Benton', 'Michael', '(403) 33-973',
  193.         'ER, Inc.', Contact);
  194.       Insert(@Contact);
  195.       SetContactValues('Wagner', 'Robert', '(906) 11-230',
  196.         'Symphony, Ltd.', Contact);
  197.       Insert(@Contact);
  198.       SetContactValues('Smith', 'John', '(656) 75-843',
  199.         'InterComm, Corp.', Contact);
  200.       Insert(@Contact);
  201.     end;
  202.   
  203.     { Dispose of the array }
  204.     Dispose(ContactList, Done);
  205.   end.
  206.  
  207. When using resizable arrays, you should always call the Insert method to insert the data into the array.  Once the data is stored in the array, you can access it directly just like with a normal compile-time array, but again, this is not recommended.
  208.  
  209. To store objects instead of records, you must use the resizable object arrays (see arrays6.pas).  Remember to call the Init method for each object that you insert into any object array!
  210.  
  211.  
  212. Sorted arrays
  213.  
  214. A sorted array is a special kind of resizable array that will sort items by key.  To use a sorted array, you must first override the KeyOf method, to let the array know how you want to sort the data.
  215.  
  216.   type
  217.     PSortedContactList = ^TSortedContactList;
  218.     TSortedContactList = object(TSortedStdArray)
  219.       function KeyOf(Item : Pointer): Pointer; virtual;
  220.     end;
  221.  
  222.   function TSortedContactList.KeyOf(Item: Pointer) : Pointer;
  223.   begin
  224.     KeyOf := @(PContact(Item)^.LastName);
  225.   end;
  226.  
  227. Now, you simply create an instance of the new array and insert items using the Insert method (see arrays7.pas).  If you want to store objects instead, you must use the sorted object arrays, which are also included.
  228.  
  229.  
  230. Stream arrays
  231.  
  232. Stream arrays are another type of array that will store the data in a stream, instead of storing it in the heap.  However, unlike memory based arrays, stream arrays work only with dynamically allocated data.
  233.  
  234.  
  235. Creating a stream array
  236.  
  237. To create a stream array, you must provide the starting and ending values for the elements' indexes, and the size of each element (just like we did with the memory based standard arrays).  In the following examples, we will use the TWeatherInfo record structure used before.
  238.  
  239.   type
  240.     PWeatherInfo = ^TWeatherInfo;
  241.     TWeatherInfo = record
  242.       Location : string[20];
  243.       Humidity : Integer;
  244.       Rain : Integer;
  245.     end;
  246.  
  247.   {...}
  248.  
  249.   begin
  250.     {...}
  251.     MorningWeatherData := New(PEmsStdArray, Init(7, 11,
  252.       SizeOf(TWeatherInfo)));
  253.     {...}
  254.     Dispose(MorningWeatherData, Done);
  255.   end.
  256.  
  257. By default, stream arrays use a TMemoryStream to store the data in the array.  If you wish to use a different type of stream, you must override the InitStream method.  This is what TEmsXXXX arrays do.
  258.  
  259.  
  260. Using a stream array
  261.  
  262. Since stream arrays work with dynamically allocated data, we must allocate each item that we will store in the array.  This is done by the SetWeatherValues procedure, which returns a pointer to the newly allocated item:
  263.  
  264.   procedure SetWeatherValues(ALocation: string; AHumidity,
  265.      ARain : Integer; var WeatherRec : PWeatherInfo);
  266.   begin
  267.     GetMem(WeatherRec, SizeOf(TWeatherInfo));
  268.     with WeatherRec^ do
  269.     begin
  270.       Location := ALocation;
  271.       Humidity := AHumidity;
  272.       Rain := ARain;
  273.     end;
  274.   end;
  275.  
  276. We now call the Insert method for each item we want to insert:
  277.  
  278.   begin
  279.     {...}
  280.     with MorningWeatherData^ do
  281.     begin
  282.       SetWeatherValues('Miami', 34, 0, WeatherInfo);
  283.       AtInsert(7, WeatherInfo);
  284.       SetWeatherValues('Helsinski', 23, 3, WeatherInfo);
  285.       AtInsert(8, WeatherInfo);
  286.       SetWeatherValues('Canada', 26, 2, WeatherInfo);
  287.       AtInsert(9, WeatherInfo);
  288.       SetWeatherValues('Berlin', 28, 5, WeatherInfo);
  289.       AtInsert(10, WeatherInfo);
  290.       SetWeatherValues('Melbourne', 20, 0, WeatherInfo);
  291.       AtInsert(11, WeatherInfo);
  292.     end;
  293.  
  294.     DisplayWeatherData(MorningWeatherData);
  295.     Writeln;
  296.     FindLowHumidityValue(MorningWeatherData);
  297.     {...}
  298.   end.
  299.  
  300.  
  301. Using DoneItem
  302.  
  303. The DoneItem method plays an important part in the use of stream arrays.  To understand better the reason for this, look at the following storage diagram for a stream array:
  304.  
  305.   (see fig7_1.gif)
  306.  
  307. When items are inserted into the array, all the information is copied to stream.  When an item is retrieved, a new copy of the item is allocated in the heap:
  308.  
  309.   (see fig7_2.gif)
  310.  
  311. If you retrieve the same item again, another copy will be created.  Therefore, you must always call DoneItem when done using a data item you retrieve from the stream array.  This is true even if you free (delete and dispose of) an item in the array.  For example, if you retrieve item 5 and then call AtFree(5), only the item in the stream will be freed; you still have to dispose of the copy that you retrieved.  This is also the reason you must allocate every item you insert into an array, since DoneItem will be automatically called for the item immediately after it is inserted into the array.  
  312.  
  313. You must consider the way stream arrays handle data when modifying items in a stream array.  When you modify an item, you are modifying the copy made when you retrieved the item and therefore, you still need to update the data in the container.  This is best achieved by means of the AtPut method.
  314.  
  315. Using DoneItem is just a matter of calling it when you no longer need an item.  As an example, look at the DisplayWeatherData procedure:
  316.  
  317.   procedure DisplayWeatherData(WeatherData: PSequence);
  318.   var
  319.     i : Integer;
  320.     Item : Pointer;
  321.   begin
  322.     with WeatherData^ do
  323.       for i := FirstIndex to LastIndex do
  324.       begin
  325.         Item := At(i);
  326.         with PWeatherInfo(Item)^ do
  327.           Writeln('Hour: ', i:2, ':00', '':3, Location,
  328.             '':20 - Length(Location), Humidity, '':5,
  329.             Rain:5);
  330.         DoneItem(Item); { required }
  331.       end;
  332.   end;
  333.  
  334. As you can see, the item is retrieved from the array, the information is displayed and finally, when the item is no longer needed, it is disposed of by a call to DoneItem.
  335.  
  336.  
  337. Object arrays
  338.  
  339. Object stream arrays let you store dynamically allocated objects in a stream.  When using the standard object stream arrays, you must follow the same usage guidelines as for the standard stream arrays.  There is another type of stream array, however, that works very differently to any other array in the library: the stream-object array.
  340.  
  341. Stream-object arrays work by using the object's Load and Store methods to store and read the data in and from the stream, respectively.  To understand the difference between a standard object stream array and a stream-object array, look at the following object:
  342.  
  343.   type
  344.     PContact = ^TContact;
  345.     TContact = object(TObject)
  346.         FirstName,
  347.         LastName,
  348.         Phone,
  349.         Company : PString;
  350.       constructor Init(ALastName, AFirstName, APhone, 
  351.         ACompany : string);
  352.       constructor Load(var S : TStream);
  353.       procedure Store(var S : TStream);
  354.       destructor Done; virtual;
  355.     end;
  356.  
  357. If you store this object in a standard object stream array, everything except the strings (which are stored somewhere else in the heap -- not in the object itself) will be stored in the stream.  However, if you store it in a stream object array, the array will call the object's Store method and therefore, store only the strings into the array.  When you retrieve the item from the array, a new instance of a TContact object will be allocated, and then its Load method will be called to retrieve the object's strings from the stream.
  358.  
  359. Stream object arrays are useful when you want to store very complex objects into a stream (for example, with pointers to other objects).  However, there are a few restrictions that must be met before using a stream object array:
  360.  
  361. - Objects must be registered:  you must register your object's type for stream access (see your Borland manuals for detailed information about registering objects).
  362.  
  363. - Objects must have Load and Store methods:  since the stream is accessed exclusively through these methods, you must make sure that your object implements these methods.
  364.  
  365. - If the objects you are storing contain pointers to other data, you must override the IndexOf method if you wish to find items using the item itself (for example, by using the Delete or Free methods).
  366.  
  367. See the ARRAYS9.PAS file for an example of how to use a stream object array.
  368.